import pandas as pd
import numpy as np
import pickle as pk
import matplotlib.pyplot as plt
#字典，用于建立用户和物品的索引
from collections import defaultdict
#稀疏矩阵，存储打分表
import scipy.io as sio
import scipy.sparse as ss
#距离
import scipy.spatial.distance as ssd
import math
import multiprocessing
from multiprocessing import Pool
from multiprocessing.managers import BaseManager

data_path = './Data/'
model_path = './model/'

# 用户和 item 的索引
with open(model_path+'train_users_index.pkl', 'rb') as fr:
    users_index = pk.load(fr)
fr.close()

with open(model_path+'train_items_index.pkl', 'rb') as fr:
    items_index = pk.load(fr)
fr.close()

# 用户和items 的倒排表
with open(model_path+'user_items.pkl', 'rb') as fr:
    user_items = pk.load(fr)
fr.close()

with open(model_path+'item_users.pkl', 'rb') as fr:
    item_users = pk.load(fr)
fr.close()

# 参与打分的用户和item数量
user_num = len(users_index)
item_num = len(items_index)
print('user num: %d' % user_num)
print('item_num: %d' % item_num)


# 用户物品关系矩阵
user_item_scores = sio.mmread(model_path+'user_item_scores.mtx')
user_item_scores = user_item_scores.tocsr()

# 计算用户对所有打过分的物品的平均打分
user_mean_score = np.zeros(user_num)
# 遍历所有的用户
for u in range(user_num):
    user_score_sum = 0.0
    items_num = 0
    # 遍历用户打过分的items
    for i in user_items[u]:
        user_score_sum += user_item_scores[u,i]
        items_num += 1
    user_mean_score[u] = user_score_sum/items_num


def user_similarity(uid1, uid2):
    # uid1 和 uid2 共同打过分的items
    common_items = {}
    for item in user_items[uid1]:
        if item in user_items[uid2]:
            common_items[item] = 1
    
    # uid1 和 uid2 都打过分的item数为0， 相似度为 0
    if len(common_items) == 0:
        return 0
    
    # 计算 uid1 对common_items 的有效打分
    uid1_valid_score = np.array([user_item_scores[uid1,item] - user_mean_score[uid1] for item in common_items])
    # 计算 uid2 对common_items 的有效打分
    uid2_valid_score = np.array([user_item_scores[uid2,item] - user_mean_score[uid2] for item in common_items])
    
    # 计算 uid1 和 uid2 的cosine相似度， ssd.cosine() 计算得到的是cosine距离
    similarity = 1-ssd.cosine(uid1_valid_score, uid2_valid_score)
    # uid1_valid_score 或者 uid2_valid_score 的模为 0，手动定义uid1 和 uid2 之间的相似度
    if np.isnan(similarity):
        similarity = 0.0
    return similarity

# args: process_num 表示进程号, user_num 表示用户数， count_4_per_precess 每个进程需要处理的用户数量,user_similarity_matrix 用户相似度矩阵
def all_user_similarity(process_num,user_num,count_4_per_precess,user_similarity_matrix_m):
    if ((process_num+1)*count_4_per_precess) < user_num:
        for ui in range(process_num*count_4_per_precess,(process_num+1)*count_4_per_precess):
            if ui%100==0:
                print('user_index i is: %d' % ui)
            user_similarity_matrix_m.set_value(ui,ui,1.0)
            for uj in range(ui+1, user_num):
                similarity = user_similarity(ui, uj)
                if math.fabs(similarity) < 0.2:
                    continue
                user_similarity_matrix_m.set_value(ui,uj,similarity)
                user_similarity_matrix_m.set_value(uj,ui,similarity)
    else:
        for ui in range(process_num*count_4_per_precess,user_num):
            if ui%100==0:
                print('user_index i is: %d' % ui)
            user_similarity_matrix_m.set_value(ui,ui,1.0)
            for uj in range(ui+1, user_num):
                similarity = user_similarity(ui, uj)
                if math.fabs(similarity) < 0.2:
                    continue
                user_similarity_matrix_m.set_value(ui,uj,similarity)
                user_similarity_matrix_m.set_value(uj,ui,similarity)
    return


# 定义一个要共享实例化对象的类
class user_similarity_matrix_manager():
    global user_num
    def __init__(self):
        print('user_num: ', user_num)
        self.user_similarity_matrix = ss.dok_matrix((user_num, user_num), dtype=np.float64)

    def set_value(self,ui,uj,value):
        self.user_similarity_matrix[ui,uj] =value

    def get_value(self,ui,uj):
        return user_similarity_matrix[ui,uj]
    
    def get_all_value(self):
        return self.user_similarity_matrix


def user_similarity_matrix_main(): 
    # 使用 BaseManager 实现多进程共享变量
    manager = BaseManager()
    # 一定要在start前注册，不然就注册无效
    manager.register('user_similarity_matrix_manager', user_similarity_matrix_manager)
    manager.start()
    user_similarity_matrix_m = manager.user_similarity_matrix_manager()
    # 进程数量 ()
    multi_process_num = math.floor(multiprocessing.cpu_count()*0.85)
    print('multi_process_num: %d' % multi_process_num)
    p = Pool(multi_process_num)  
    count_4_per_precess = math.ceil(user_num / multi_process_num)   # 每个进程需要处理的人数
    for process_num in range(multi_process_num):
        p.apply_async(all_user_similarity,args=(process_num,user_num,count_4_per_precess,user_similarity_matrix_m))
    p.close()
    p.join()
    print('all subprocessed done')
    return user_similarity_matrix_m

user_similarity_matrix_m = user_similarity_matrix_main()

user_similarity_matrix = user_similarity_matrix_m.get_all_value()

with open(model_path+'user_similarity_matrix.pkl', 'wb') as fw:
    pk.dump(user_similarity_matrix, fw)
fw.close()

print(type(user_similarity_matrix))







